import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

const double defaultSpeed = 1200;

class SmoothArgs {
  ScrollController controller;
  ScrollPhysics physics;
  bool enable;

  SmoothArgs(
      {required this.controller, required this.physics, required this.enable});
}

typedef SmoothChildBuilder = Widget Function(
    BuildContext context, SmoothArgs args);

class SmoothScrollWeb extends StatelessWidget {
  ScrollController controller = ScrollController();

  SmoothChildBuilder builder;

  /// px/s
  final double speed;

  final Curve curve;

  bool enable;

  double _scroll = 0;

  SmoothScrollWeb({
    Key? key,
    required this.builder,
    this.speed = defaultSpeed,
    this.curve = Curves.easeOutCubic,
    ScrollController? controller,
    this.enable = kIsWeb,
  }) : super(key: key) {
    if (controller != null) this.controller = controller;
  }

  @override
  Widget build(BuildContext context) {
    controller.addListener(() {
      if (controller.position.activity is IdleScrollActivity) {
        _scroll = controller.position.extentBefore;
      }
    });

    return Listener(
      onPointerSignal: onPointerSignal,
      child: builder(
          context,
          SmoothArgs(
              controller: controller,
              enable: enable,
              physics: enable
                  ? const NeverScrollableScrollPhysics()
                  : const BouncingScrollPhysics())),
    );
  }

  void onPointerSignal(PointerSignalEvent pointerSignal) {
    if (enable && pointerSignal is PointerScrollEvent) {
      double distance = pointerSignal.scrollDelta.dy;
      if (_scroll + pointerSignal.scrollDelta.dy < 0) {
        distance = _scroll;
        _scroll = 0;
      } else if (_scroll + pointerSignal.scrollDelta.dy >
          controller.position.maxScrollExtent) {
        distance = controller.position.maxScrollExtent - _scroll;
        _scroll = controller.position.maxScrollExtent;
      } else {
        _scroll += pointerSignal.scrollDelta.dy;
      }
      double duration = distance / speed * 1000;
      int milliseconds = duration.abs().round();
      milliseconds > 9
          ? controller.animateTo(
              _scroll,
              duration: Duration(
                milliseconds: milliseconds,
              ),
              curve: curve,
            )
          : controller.jumpTo(_scroll);
    }
  }
}
